Skip to content

fix: extract nested JS function declarations (#2653) - #2705

Closed
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:fix/2653-nested-function-declarations
Closed

fix: extract nested JS function declarations (#2653)#2705
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:fix/2653-nested-function-declarations

Conversation

@hopstreax

Copy link
Copy Markdown
Contributor

Summary

Fixes #2653.

Named JavaScript/TypeScript function declarations nested inside another function were not emitted as graph nodes. As a result, calls from nested functions could be attributed to the enclosing function or become dangling edges.

For example:

function doThing() {}

export function Panel() {
  function handleClick() {
    doThing()
  }

  return <button onClick={handleClick} />
}

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

This PR adds handling for named function declarations nested inside other JavaScript/TypeScript functions in the generic extractor (_extract_generic). A new recursive _scan_js_nested_functions helper walks function bodies to emit nodes for nested named functions, wire up contains edges from their enclosing function, track them as callable definitions, and register their bodies for further descent. It also changes _tracked_body_ids to key on body node objects directly rather than id(...). On the test side, it adds three new tests in test_extract.py covering single-level and deeply nested function declarations plus local variable suppression, and updates an existing test in test_js_dynamic_imports.py so nested-function calls now attribute to the inner function (with a contains edge from the outer) rather than the enclosing function. Reviewers may want to focus on the recursive scan logic, the interaction with existing closure-descent tracking (#1630/#2575), and the behavioral shift in call attribution reflected by the modified dynamic-imports test.

No blocking issues surfaced. 4 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 887 functions depend on the 524 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: extract_js() — 76 callers, 3 callees
  • worse: walk() — 1 callers, 55 callees
  • new: _scan_js_nested_functions() — 1 callers, 7 callees

Verification — 887 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 832 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 1 grounded finding(s) anchored inline below; 2 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extractors/engine.py
safishamsi added a commit that referenced this pull request Aug 14, 2026
…acks (#2653)

#2705 fixed `function` nested in a `function`, but the React idiom that
motivated the issue -- a function declared inside an arrow-defined component
(`const Panel = () => { function handleClick(){} }`) or an arrow callback
(`useEffect(() => { function h(){} })`) -- was still missed: the main walk
never recurses into arrow bodies and the scan bailed at the arrow boundary.

Refactors the inline scan into a module-level _scan_js_nested_function_
declarations that also descends through arrow / function-expression bodies
(attributing nested declarations to the nearest enclosing named scope), and
invokes it from the const-arrow branch of _js_extra_walk. Nested bodies join
function_bodies, so the central _tracked_body_ids guard prevents double-walk.
Adds tests for both arrow idioms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hopstreax

Copy link
Copy Markdown
Contributor Author

Glad to see this got extended to cover arrow-defined components and callbacks as well. My PR addressed the nested named-function declaration case, and this follow-up nicely covers the React patterns that motivated the issue. Thanks for building on it!

@safishamsi

Copy link
Copy Markdown
Collaborator

Landed in v0.9.44, just published to PyPI. I cherry-picked this onto v8 with your authorship preserved in the commit, so both the change and the credit are in the history. Thanks @hopstreax for the fix. Closing since it is now released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TS/TSX: function declarations nested inside another function emit no node, so their call edges dangle

2 participants